"use client"; /** * /w/:workspace/activity — the SPACE activity feed (T-158, spec §14 "space * activity feed"). * * Membership-gated (GET /workspaces/:id/activity → 403 for non-members, the * same no-leak contract as stats/archive): the server is authoritative, this * page only renders what the caller may see. A non-member never reaches the * feed — the [workspace] LAYOUT bounces them (isError → /w/default, the * house pattern shared with /search, /ask, /inbox), so there is no in-page * "no access" state to render. Rows deep-link within the current workspace. * "My activity" (cross-workspace) lives at /activity. */ import { useCallback } from "react"; import { useParams } from "next/navigation"; import { Activity, ArrowUpRight, Loader2 } from "lucide-react"; import { workspaceActivity } from "@/lib/api"; import { useWorkspace } from "@/hooks/use-workspace"; import { ActivityFeed } from "@/components/activity/activity-feed"; import type { ActivityRow } from "@/lib/types"; export default function SpaceActivityPage() { const params = useParams<{ workspace: string }>(); const slugOrId = params?.workspace ?? ""; const { data: ws, isLoading } = useWorkspace(slugOrId); const fetchPage = useCallback( (cursor?: string) => workspaceActivity(ws?.id ?? "", { cursor, limit: 30 }), [ws?.id] ); // Deep links use the WORKSPACE ID (the route param here is the raw // `[workspace]` segment — id or slug; id links keep the selector contract // stable for whatever the URL carries). const hrefFor = useCallback((row: ActivityRow): string | null => { if (!ws) return null; if (row.pageId) return `/w/${ws.id}/page/${row.pageId}`; if (row.resourceType === "database") return `/w/${ws.id}/databases/${row.resourceId}`; return null; }, [ws]); return (
What happened in {ws?.name ?? "this space"}.{" "}
My activity